//                 _              
//                | |             
//   __ _ _ __ ___| |__ __  _____ 
//  / _` | '__/ __| '_ \\ \/ / __|
// | (_| | | | (__| | | |>  <\__ \
//  \__,_|_|  \___|_| |_/_/\_\___/
//                                
// Copyright 2010 Scott Dixon http://www.archxs.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.archxs.commons;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;


/**
 * Il n'y a pas de hors-texte.
 */
public class Variant extends Number {

    /**
     * Serial UID.
     */
    private static final long serialVersionUID = -3646278268577717034L;

    /**
     * The string that is the Variant equivalent to Java's <code>null</code> type.
     */
    public final static String NULL_NAME = "null";
    
    /**
     * Handle all (that I know about) unicode code points that are spaces.
     */
    static final int[] UNICODE_SPACES = new int[] {
        0x0020, // Space
        0x00A0, // Non-breaking space
        0x00B7, // Interpunct
        0x1680, // [http://en.wikipedia.org/wiki/Ogham Ogham] Space Mark
        0x180E, // Mongolian Vowel Separator
        0x2002, // EN Space
        0x2003, // EM space
        0x2004, // Three-per-EM space
        0x2005, // Four-per-EM space
        0x2006, // Six-per-EM space
        0x2007, // Figure Space
        0x2008, // Punctuation Space
        0x2009, // Thin Space
        0x200A, // Hair Space
        0x200B, // Zero width space
        0x200C, // Zero width, non-joiner space
        0x200D, // Zero width, joiner space
        0x202F, // Narrow non-breaking space
        0x205F, // Medium Mathematical space
        0x2060, // Word Joiner
        0x2420, // Space Symbol
        0x2422, // Blank Symbol
        0x2423, // Open box
        0x3000, // Ideographic space
    };

    private static boolean isUnicodeSpace(int value) {
        final int insertionPoint = Arrays.binarySearch(UNICODE_SPACES, value);
        return (insertionPoint >= 0 && insertionPoint < UNICODE_SPACES.length);
    };
    
    //TODO: change to two methods; nextNonWhitespace(String, int) previousNonWhitespace(String,int)
    static String unicodeTrim(final String input) {
        final int last = input.length() - 1;
        int end = last;
        int start = 0;
        if (start < end) {
            int codePoint;
            while(start < end) {
                codePoint = input.charAt(start);
                if (Character.isWhitespace(codePoint) || isUnicodeSpace(codePoint)) {
                    ++start;
                } else {
                    break;
                }
            }
            while(end > start) {
                codePoint = input.charAt(end);
                if (Character.isWhitespace(codePoint) || isUnicodeSpace(codePoint)) {
                    --end;
                } else {
                    break;
                }
            }
            if (start > 0 || end < last) {
                return input.substring(start,end + 1);
            }
        } 
        return input;
    }

    /**
     * Raw variant data.
     */
    private final String m_rawValue;

    /**
     * Cached length of array representation.
     */
    private final AtomicInteger m_arrayLen;
    
    /**
     * The locale for this variant. Affixed at object construction.
     */
    private final Locale m_locale;
    
    /**
     * TODO: build infrastructure for changing this value. Use URLStreamHandlerFactory
     * as a design prototype.
     */
    private final static AtomicReference<Class<? extends Variant> > m_variantType = new AtomicReference<Class<? extends Variant> >(Variant.class);
    
    // +-----------------------------------------------------------------------+
    // | CONSTRUCTORS/FACTORIES
    // +-----------------------------------------------------------------------+
    /**
     * Create a new Variant from a value in a resource bundle.
     * @param bundle The bundle to get the raw string from.
     * @param key    The key of the value in the resource bundle.
     * @return A Variant with the value from the given resource bundle or {@code null}
     *         if the given value could not be found.
     * @throws IllegalArgumentException Thrown if {@code bundle} or {@code key} 
     *         arguments are {@code null}.
     * @throws MissingResourceException Thrown if the key is not found in the bundle.
     *         use {@link #getPropertyAsVariant(ResourceBundle, String, String)} 
     *         to avoid handling this exception.
     */
    public static Variant getPropertyAsVariant(final ResourceBundle bundle, final String key) {
        if (null == bundle || null == key) {
            throw new IllegalArgumentException("arguments were null.");
        }
        return valueOf(bundle.getString(key), bundle.getLocale());
    }
    
    /**
     * Create a new Variant from a value in a resource bundle.
     * @param bundle       The bundle to get the raw string from.
     * @param key          The key of the value in the resource bundle.
     * @param defaultValue The default value to use for the Variant if no value 
     *                     could be found in the resource bundle.
     * @return A Variant.
     * @throws IllegalArgumentException Thrown if {@code bundle} or {@code key} 
     *         arguments are {@code null}.
     */
    public static Variant getPropertyAsVariant(final ResourceBundle bundle, final String key, final String defaultValue) {
        if (null == bundle || null == key) {
            throw new IllegalArgumentException("arguments were null.");
        }
        try {
            return getPropertyAsVariant(bundle,key);
        } catch (final Exception e) {
            return valueOf(defaultValue, bundle.getLocale());
        }
    }
    
    /**
     * Get a Variant for a given string.
     * @param  rawValue        The raw value to be transformed into various Java values.
     */
    public static Variant valueOf(final String rawValue) {
        return valueOf(getVariantType(), rawValue);
    }
    
    /**
     * Get a Variant for a given array of strings.
     * @param  rawValue        The raw value to be transformed into various Java values.
     */
    public static Variant valueOf(final String[] rawValue) {
        return valueOf(getVariantType(), rawValue);
    }
    
    /**
     * Get a Variant for a given string.
     * @param  rawValue        The raw value to be transformed into various Java values.
     * @param  locale          The locale for the Variant to be retrieved.
     */
    public static Variant valueOf(final String rawValue, final Locale locale) {
        return valueOf(getVariantType(), rawValue, locale);
    }
    
    /**
     * Get a Variant for a given array of strings.
     * @param  rawValue        The raw value to be transformed into various Java values.
     * @param  locale          The locale for the Variant to be retrieved.
     */
    public static Variant valueOf(final String[] rawValue, final Locale locale) {
        return valueOf(getVariantType(), rawValue, locale);
    }
    
    /**
     * Get a Variant for a given string.
     * @param  type            The Variant type to use.
     * @param  rawValue        The raw value to be transformed into various Java values.
     */
    public static Variant valueOf(final Class<? extends Variant> type, final String rawValue) {
        return VariantObjectCacheFactory.getInstance().readThroughCache(type, rawValue);
    }
    
    /**
     * Get a Variant for a given array of strings.
     * @param  type            The Variant type to use.
     * @param  rawValue        The raw value to be transformed into various Java values.
     */
    public static Variant valueOf(final Class<? extends Variant> type, final String[] rawValue) {
        return VariantObjectCacheFactory.getInstance().readThroughCache(type, rawValue);
    }
    
    /**
     * Get a Variant for a given string.
     * @param  type            The Variant type to use.
     * @param  rawValue        The raw value to be transformed into various Java values.
     * @param  locale          The locale for the Variant to be retrieved.
     */
    public static Variant valueOf(final Class<? extends Variant> type, final String rawValue, final Locale locale) {
        return VariantObjectCacheFactory.getInstance().readThroughCache(type, rawValue, locale);
    }
    
    /**
     * Get a Variant for a given array of strings.
     * @param  type            The Variant type to use.
     * @param  rawValue        The raw value to be transformed into various Java values.
     * @param  locale          The locale for the Variant to be retrieved.
     */
    public static Variant valueOf(final Class<? extends Variant> type, final String[] rawValue, final Locale locale) {
        return VariantObjectCacheFactory.getInstance().readThroughCache(type, rawValue, locale);
    }
    
    /**
     * Cast a Variant to another type creating a new object if necessary within the
     * default locale. Specifically {@code convertFrom} may or may not point to the 
     * same object returned depending on the type being cast to.
     * @param  <T>           The type to cast to.
     * @param  convertTo     The type to convert to. Required thanks to Java erasure.
     * @param  convertFrom   The value to cast.
     * @return Either the {@code convertFrom} value or a new object.
     * @throws ClassCastException If the cast was invalid or was unimplementated.
     */
    public static <T> T dynamic_cast(final Class<? extends T> convertTo, final Variant convertFrom) {
        return dynamic_cast(convertTo, convertFrom, Locale.getDefault());
    }
    
    /**
     * Cast a Variant to another type creating a new object if necessary. Specifically
     * {@code convertFrom} may or may not point to the same object returned depending
     * on the type being cast to.
     * @param  <T>           The type to cast to.
     * @param  convertTo     The type to convert to. Required thanks to Java erasure.
     * @param  convertFrom   The value to cast.
     * @param  locale        The locale within which to perform the cast.
     * @return Either the {@code convertFrom} value or a new object.
     * @throws ClassCastException If the cast was invalid or was unimplementated.
     */
    @SuppressWarnings("unchecked")
    public static <T> T dynamic_cast(final Class<? extends T> convertTo, final Variant convertFrom, final Locale locale) {
        if (null == convertTo) {
            return null;
        } else if (null == convertFrom) {
            if (Variant.class.isAssignableFrom(convertTo)) {
                return (T)VariantObjectCacheFactory.getInstance().readThroughCache((Class<? extends Variant>)convertTo, (String)null, locale);
            } else if (Variant[].class.isAssignableFrom(convertTo)) {
                final Class<? extends Variant> componentType = (Class<? extends Variant>)convertTo.getComponentType();
                final Variant v = VariantObjectCacheFactory.getInstance().readThroughCache(componentType, (String)null, locale);
                if (null == v) {
                    return null;
                } else {
                    return (T)v.getArray();
                }
            } else {
                return null;
            }
        } else if (convertTo.isArray()) {
            final Variant[] convertFromArray = convertFrom.getArray();
            final Class<?> convertToComponentType = convertTo.getComponentType();
            
            if (convertToComponentType.isPrimitive()) {
                // SPECIAL Void KEYWORD HANDLING
                if (convertToComponentType == Void.TYPE) {
                    throw new ClassCastException("Cannot cast to a void[]");
                }
                // CONVERT TO ONE OF THE EIGHT PRIMATIVE TYPES (boolean, byte, char, short, int, long, float, double)
                else if (convertToComponentType == Boolean.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    boolean[] convertToArray = (boolean[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].booleanValue();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Byte.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    byte[] convertToArray = (byte[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].byteValue();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Character.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    char[] convertToArray = (char[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].toCharacter();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Short.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    short[] convertToArray = (short[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].shortValue();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Integer.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    int[] convertToArray = (int[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].intValue();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Long.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    long[] convertToArray = (long[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].longValue();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Float.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    float[] convertToArray = (float[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].floatValue();
                    }
                    return (T)convertToArray;
                } else if (convertToComponentType == Double.TYPE) {
                    // CREATE PRIMATIVE BOOLEAN ARRAY
                    double[] convertToArray = (double[])Array.newInstance(convertToComponentType, convertFromArray.length);
                    for(int i = 0; i < convertToArray.length; ++i) {
                        convertToArray[i] = convertFromArray[i].doubleValue();
                    }
                    return (T)convertToArray;
                } else {
                    throw new IllegalArgumentException(String.format("Unknown Java primiative type \"%s\" encountered!", convertToComponentType.getName()));
                }
            } else {
                // CREATE OBJECT ARRAY
                final Object[] convertToArray = (Object[]) Array.newInstance(convertToComponentType, convertFromArray.length);
                for(int i = 0; i < convertToArray.length; ++i) {
                    convertToArray[i] = dynamic_cast(convertToComponentType, convertFromArray[i]);
                }
                return (T)convertToArray;
            }
        } else if (convertTo == String.class) {
            return convertTo.cast(convertFrom.toString());
        } else if (convertTo == Integer.class) {
            return convertTo.cast(convertFrom.intValue());
        } else if (convertTo == int.class) {
            return (T)Integer.valueOf(convertFrom.intValue());
        } else if (convertTo == Float.class) {
            return convertTo.cast(convertFrom.floatValue());
        } else if (convertTo == float.class) {
            return (T)Float.valueOf(convertFrom.floatValue());
        } else if (convertTo == Double.class) {
            return convertTo.cast(convertFrom.doubleValue());
        } else if (convertTo == double.class) {
            return (T)Double.valueOf(convertFrom.doubleValue());
        } else if (convertTo == Long.class) {
            return convertTo.cast(convertFrom.longValue());
        } else if (convertTo == long.class) {
            return (T)Long.valueOf(convertFrom.longValue());
        } else if (convertTo == Boolean.class) {
            return convertTo.cast(convertFrom.booleanValue());
        } else if (convertTo == boolean.class) {
            return (T)Boolean.valueOf(convertFrom.booleanValue());
        } else if (convertTo == Character.class) {
            return convertTo.cast(convertFrom.toCharacter());
        } else if (convertTo == char.class) {
            return (T)Character.valueOf(convertFrom.toCharacter());
        } else if (convertTo == Short.class) {
            return convertTo.cast(convertFrom.shortValue());
        } else if (convertTo == short.class) {
            return (T)Short.valueOf(convertFrom.shortValue());
        } else if (convertTo == Byte.class) {
            return convertTo.cast(convertFrom.byteValue());
        } else if (convertTo == byte.class) {
            return (T)Byte.valueOf(convertFrom.byteValue());
        } else if (convertTo == Void.class) {
            throw new ClassCastException("Cannot cast to void.");
        }  else if (convertTo == void.class) {
            throw new ClassCastException("Cannot cast to void.");
        } else {
            return convertTo.cast(convertFrom);
        }
    }
    
    /**
     * Get the current implementation class for Variants in the caller's context.
     * It has not yet been determined what the scope of the "context" will
     * be. Choices are JVM-wide, per-classloader, or per-threadgroup.
     * 
     * @return The current Variant implementation class for the caller's context
     *         (i.e. the type that will be instantiated for the calling code if 
     *         it should invoke any of the {@link #valueOf(String)} overrides. 
     */
    public static Class<? extends Variant> getVariantType() {
        return m_variantType.get();
    }
    
    /**
     * Create a new Variant. Override this constructor and call the Variant baseclass
     * when extending this class. <pre>
     *    public class MyVariant extends Variant {
     *    
     *        //Required constructor.
     *        protected MyVariant(final String rawValue, final Locale locale) {
     *            super(rawValue, locale);
     *            // Now do whatever else you want...
     *        }
     *    }
     * </pre>
     * @param  rawValue        The raw value to be transformed into various Java values.
     * @param  locale          The locale for this Variant.
     */
    protected Variant(final String rawValue, final Locale locale) {
        m_rawValue = rawValue;
        m_arrayLen = new AtomicInteger(-1);
        m_locale = locale;
    }
    
    /**
     * Create a new Variant.
     * @param  rawValue        The raw value to be transformed into various Java values.
     * @param  locale          The locale for this Variant.
     */
    Variant(final String rawValue) {
        this(rawValue, Locale.getDefault());
    }
    
    /**
     * Create a new Variant.
     * @param  rawArrayValue   An array to store in "stringified" (i.e. serialized)
     *                         form as the variant's rawValue.
     */
    Variant(final String[] rawArrayValue) {
        this(rawArrayValue,Locale.getDefault());
    }
    
    /**
     * Create a new Variant.
     * @param  rawArrayValue   An array to store in "stringified" (i.e. serialized)
     *                         form as the variant's rawValue.
     * @param  locale          The locale for this Variant.
     */
    Variant(final String[] rawArrayValue, final Locale locale) {
        this(arrayToString(rawArrayValue, locale), locale);
        m_arrayLen.set((null == rawArrayValue)?1:rawArrayValue.length);
    }
    

    /**
     * Returns a string that is converted into an intermediate format suitable for
     * further parsing into other types. This often includes changing the case of
     * the string and trimming any leading or trailing whitespace.
     * 
     * @param  normalizationLocale The local to use when normalizing the string.
     * @return Normalization of the Variant's source string.
     */
    public final String getNormalizedString(final Locale normalizationLocale) {
        if (null == m_rawValue) {
            return null;
        }
        //TODO: integrate trim and array notation detection logic for efficiency.
        String adjustedRawValue = unicodeTrim(m_rawValue);
        if (adjustedRawValue.startsWith("[")) {
            if (adjustedRawValue.endsWith("]")) {
                adjustedRawValue = adjustedRawValue.substring(1, adjustedRawValue.length() - 1).trim();
            } else {
                adjustedRawValue = adjustedRawValue.substring(1).trim();
            }
        }
        final int firstComma = adjustedRawValue.indexOf(',');
        if (firstComma == 0 || (firstComma > 0 && adjustedRawValue.indexOf('/') != (firstComma - 1))) {
            adjustedRawValue = adjustedRawValue.substring(0, firstComma);
        } 
        return unicodeTrim(adjustedRawValue).toLowerCase(normalizationLocale);
    }
    
    /**
     * Returns a string that is converted into an intermediate format suitable for
     * further parsing into other types. This often includes changing the case of
     * the string and trimming any leading or trailing whitespace.
     * 
     * @return Normalization of the Variant's source string.
     */
    public final String getNormalizedString() {
        return getNormalizedString(m_locale);
    }
    
    /**
     * Get the locale for this Variant instance.
     * @return The locale for this Variant object.
     */
    public Locale getLocale() {
        return m_locale;
    }
    
    // +-----------------------------------------------------------------------+
    // | Object
    // +-----------------------------------------------------------------------+
    @Override
    public String toString() {
        return stringValue();
    }
    
    @Override
    public int hashCode() {
        return stringValue().hashCode();
    }
    
    @Override
    public boolean equals(final Object rgt) {
        if (null == rgt) {
            return false;
        }
        if (rgt == this) {
            return true;
        }
        if (!(rgt instanceof Variant)) {
            return false;
        }
        return toString().equals(((Variant)rgt).toString());
    }
    
    // +-----------------------------------------------------------------------+
    // | STRING
    // +-----------------------------------------------------------------------+
    /**
     * Get the variant's value as a String.
     * @return The variant's value as a string.
     */
    public String stringValue() {
        if (null == m_rawValue) {
            return "null";
        }
        return m_rawValue;
    }
    
    // +-----------------------------------------------------------------------+
    // | CHARACTER
    // +-----------------------------------------------------------------------+
    /**
     * Get the variant's value as a character. 
     */
    public char toCharacter() {
        final String normalized = getNormalizedString();
        long longVal = longValueInternal(normalized);
        if (longVal != 0) {
            return (char)longVal;
        }
        // TODO: document the behavior for MBS
        return normalized.charAt(0);
    }
    
    // +-----------------------------------------------------------------------+
    // | ARRAY
    // +-----------------------------------------------------------------------+
    /**
     * Get the length of the variant if converted into an array.
     * @return The length of the array representation of this Variant.
     */
    public int getArrayLength() {
        int length = m_arrayLen.get();
        if (-1 >= length) {
            length = getArray().length;
            m_arrayLen.getAndSet(length);
        }
        return length;
    }
    
    /**
     * Get the variant's value as an array.
     * @return An array of Variants.
     */
    public Variant[] getArray() {
        return stringToArray(getClass(), m_rawValue, m_locale);
    }
    
    /**
     * Get the variant's value as an array of the given type.
     * @param <T>  The type of array to return.
     * @param a    An array to populate and return if large enough to fit the
     *             array aspect of this Variant. If this array is too small it 
     *             will not be modified. If it is overly large then the element
     *             after the last value modified will be set to {@code null}
     * @return A typed array.
     * @throws ArrayStoreException - if the runtime type of the specified array is not a supertype of the runtime type of every element in this collection 
     * @throws NullPointerException - if the specified array is null
     */
    @SuppressWarnings("unchecked")
    public <T> T[] getArray(T[] a) {
        final Variant[] array = getArray();
        final Class<? extends T> type = (Class<? extends T>)a.getClass().getComponentType();
        if (a.length < array.length) {
            a = (T[])Array.newInstance(type, array.length);
        }
        int i = 0;
        for(;i < array.length; ++i) {
            a[i] = Variant.<T>dynamic_cast(type,array[i]);
        }
        if (i < a.length) {
            a[i] = null;
        }
        return a;
    }
    
    // +-----------------------------------------------------------------------+
    // | Number                                                                |
    // +-----------------------------------------------------------------------+
    @Override
    public double doubleValue() {
        final String normalizedValue = getNormalizedString();
        if (null == normalizedValue) {
            return 0;
        }
        try {
            if (canConvertToDouble(normalizedValue)) {
                return convertToDouble(normalizedValue);
            } else if (normalizedValue.endsWith("l")) {
                return (double)Long.parseLong(normalizedValue.substring(0,normalizedValue.length() - 1));
            } else if (couldBeHex(normalizedValue) || couldBeOctal(normalizedValue)) {
                return (double)longValue();
            } else if (normalizedValue.endsWith("f")) {
                return floatValueInternal();
            } else {
                return Double.parseDouble(normalizedValue);
            }
        } catch (final NumberFormatException e) {
            if (canConvertToDouble(normalizedValue)) {
                return convertToDouble(normalizedValue);
            }
            return 0.0d;
        }
    }

    @Override
    public float floatValue() {
        try {
            return floatValueInternal();
        } catch (final NumberFormatException e) {
            return (float)doubleValue();
        }
    }

    @Override
    public int intValue() {
        return (int)Math.max(Integer.MIN_VALUE,Math.min(Integer.MAX_VALUE,longValue()));
    }

    @Override
    public long longValue() {
        final String normalizedValue = getNormalizedString();
        return longValueInternal(normalizedValue);
    }
    
    // +-----------------------------------------------------------------------+
    // | BOOLEAN
    // +-----------------------------------------------------------------------+
    /**
     * Get the variant as a boolean. This method parses the variant in the following order:
     * <ol>
     *   <li>is "true" (case insensitive)</li>
     *   <li>java.lang.parseBoolean(String)</li>
     *   <li>if the parseBoolean returns false then this method uses longValue() > 1 as true and <= 0 as false</li>
     * </ol>
     * @return The best boolean transformation of the Variant.
     */
    public boolean booleanValue() {
        final String normalized = getNormalizedString();
        if (normalized.equalsIgnoreCase("true")) {
            return true;
        } else if (Boolean.parseBoolean(normalized)) {
            return true;
        } else if (longValue() >= 1) {
            return true;
        } else {
            return false;
        }
    }
    
    // +-----------------------------------------------------------------------+
    // | VARIANT EXTENSION
    // +-----------------------------------------------------------------------+
    protected boolean canConvertToLong(final String normalizedValue) {
        return false;
    }
    
    protected long convertToLong(final String normalizedValue) {
        throw new UnsupportedOperationException(String.format("Cannot convert \"%s\" to a java.lang.Long.", normalizedValue));
    }
    
    protected boolean canConvertToDouble(final String normalizedValue) {
        return false;
    }
    
    protected double convertToDouble(final String normalizedValue) {
        throw new UnsupportedOperationException(String.format("Cannot convert \"%s\" to a java.lang.Double.", normalizedValue));
    }
    
    // +-----------------------------------------------------------------------+
    // | PRIVATE
    // +-----------------------------------------------------------------------+
    private long longValueInternal(final String normalizedValue) {
        if (null == normalizedValue) {
            return 0L;
        }
        try {
            if (canConvertToLong(normalizedValue)) {
                return convertToLong(normalizedValue);
            } else if (normalizedValue.endsWith("l")) {
                return Long.parseLong(normalizedValue.substring(0,normalizedValue.length() - 1));
            } else if (couldBeHex(normalizedValue)) {
                return Long.parseLong(normalizedValue.substring(2), 16);
            } else if (couldBeOctal(normalizedValue)) {
                return Long.parseLong(normalizedValue.substring(1), 8);
            } if (normalizedValue.endsWith("f")) {
                return Math.round(floatValue());
            } else if (normalizedValue.endsWith("d") || normalizedValue.contains(".")) {
                return Math.round(doubleValue());
            } else {
                return Long.parseLong(normalizedValue);
            }
        } catch (final NumberFormatException e) {
            if (canConvertToLong(normalizedValue)) {
                return convertToLong(normalizedValue);
            }
            return 0L;
        }
    }
    
    private float floatValueInternal() throws NumberFormatException {
        final String normalizedValue = getNormalizedString();
        if (null == normalizedValue) {
            return 0;
        }
        if (!couldBeHex(normalizedValue) && normalizedValue.endsWith("f")) {
            return  Float.parseFloat(normalizedValue);
        } else {
            return (float)doubleValue();
        }
    }
    
    static boolean couldBeHex(final String normalizedValue) {
        if (null == normalizedValue) {
            return false;
        }
        return (normalizedValue.startsWith("0x") && !normalizedValue.contains("."));
    }
    
    static boolean couldBeOctal(final String normalizedValue) {
        if (null == normalizedValue) {
            return false;
        }
        return (!couldBeHex(normalizedValue) && normalizedValue.startsWith("0") && !normalizedValue.contains("."));
    }
    
    /**
     * Note that this method will produce the same results as {@link Arrays#toString(String[])}
     * unless there are commas in the string. This method produces reversible results 
     * so all existing commas are escaped (i.e. "," becomes "\,") before producing the
     * string representation of the array.
     */
    static String arrayToString(final String[] stringArray, final Locale locale) {
        if (null == stringArray) {
            return null;
        }
        if (stringArray.length == 1) {
            return stringArray[0];
        }
        final StringBuilder stringifiedArray = new StringBuilder();
        stringifiedArray.append('[');
        final Pattern rawDelinator = Pattern.compile("\\,");
        for(int i = 0; i < stringArray.length; ++i) {
            stringifiedArray.append(rawDelinator.matcher(stringArray[i]).replaceAll("\\\\,"));
            if (i < stringArray.length - 1) {
                stringifiedArray.append(", ");
            }
        }
        stringifiedArray.append(']');
        return stringifiedArray.toString();
    }
    
    static Variant[] stringToArray(final Class<? extends Variant> type, String stringifiedArray, final Locale locale) {
        if (null == stringifiedArray) {
            final Variant[] array = (Variant[])Array.newInstance(type, 1);
            array[0] = VariantObjectCacheFactory.getInstance().readThroughCache(type, (String)null, locale);
            return array;
        } else {
            if (stringifiedArray.length() >= 2 && 
                stringifiedArray.charAt(0) == '[' && 
                stringifiedArray.charAt(stringifiedArray.length() - 1) == ']' ) {
                stringifiedArray = stringifiedArray.substring(1, stringifiedArray.length() - 1);
            }
            final String[] stringArray = stringifiedArray.split("(?<!\\\\)\\, ?");
            final Variant[] variantArray = new Variant[stringArray.length];
            final Pattern unescapePattern = Pattern.compile("\\\\\\,");
            for(int i = 0; i < stringArray.length; ++i) {
                variantArray[i] = Variant.valueOf(unescapePattern.matcher(stringArray[i]).replaceAll(","), locale);
            }
            return variantArray;
        }
        
    }
}
